///////////////////////////////////////////////////////////////////////////////
// Include
#include <netSocket.h>
#include <netJSON.h>

///////////////////////////////////////////////////////////////////////////////
// Const
#define JSON_MAX_KEY_LENGTH 32
#define JSON_MAX_VALUE_LENGTH 128

///////////////////////////////////////////////////////////////////////////////
// Types

// JSON parser state
typedef enum 
{
	netJSONPS_NoJSON,
	netJSONPS_LookingForKey,
	netJSONPS_StoringKey,
	netJSONPS_LookingForSeparator,
	netJSONPS_LookingForValue,
	netJSONPS_StoringNumericValue,
	netJSONPS_StoringStringValue,

	netJSONPS_Failed,
	netJSONPS_Finished

} netJSONParseState;


///////////////////////////////////////////////////////////////////////////////
// Module global variables
static dosConstString l_json_line_end = "\r\n";
static dosBool l_add_separator;
static dosBool l_add_array_element_separator = dosFalse;

///////////////////////////////////////////////////////////////////////////////
// Local functions
static netJSONParseState CallJSONParserCallbackFunction(dosString in_key, dosString in_value_buffer, dosStringLength in_value_buffer_length, dosStringLength* inout_pos, netJSONParseState in_state, netJSONCallbackInfo in_callback[], void* in_user_data);


/*****************************************************************************/
/* JSON Creation routines                                                    */
/*****************************************************************************/


///////////////////////////////////////////////////////////////////////////////
// Starts JSON generation
void netJSONStart(void)
{
	l_add_separator = dosFalse;
	l_add_array_element_separator = dosFalse;
}

///////////////////////////////////////////////////////////////////////////////
// Starts a new JSON object
dosStringLength netJSONObjectStart(dosString out_buffer, dosStringLength in_buffer_length, dosStringLength in_pos, dosConstString in_name)
{
	if(l_add_separator)
		in_pos = netJSONAddSeparator(out_buffer, in_buffer_length, in_pos, &l_add_array_element_separator);

	if(in_pos != 0 )
		in_pos = strCopyConstString(out_buffer, in_buffer_length, in_pos, l_json_line_end);

	if( in_name != DOS_NULL && strGetConstLength(in_name) != 0 )
	{
		in_pos = strCopyConstString(out_buffer, in_buffer_length, in_pos, in_name);
		in_pos = strCopyConstString(out_buffer, in_buffer_length, in_pos, ": ");
	}

	in_pos = strCopyConstString(out_buffer, in_buffer_length, in_pos, "{ ");

	l_add_separator = dosFalse;

	return in_pos;
}

///////////////////////////////////////////////////////////////////////////////
// Ends a JSON object
dosStringLength netJSONObjectEnd(dosString out_buffer, dosStringLength in_buffer_length, dosStringLength in_pos)
{
	in_pos = strCopyCharacter(out_buffer, in_buffer_length, in_pos, '}');

	l_add_array_element_separator = dosTrue;
	l_add_separator = dosTrue;

	return in_pos;
}

///////////////////////////////////////////////////////////////////////////////
// Starts a new JSON array
dosStringLength netJSONArrayStart(dosString out_buffer, dosStringLength in_buffer_length, dosStringLength in_pos, dosConstString in_name)
{
	dosBool add_separator = l_add_separator || l_add_array_element_separator;

	in_pos = netJSONAddSeparator(out_buffer, in_buffer_length, in_pos, &add_separator);
	in_pos = strCopyCharacter(out_buffer, in_buffer_length, in_pos, '\"');
	in_pos = strCopyConstString(out_buffer, in_buffer_length, in_pos, in_name);
	in_pos = strCopyCharacter(out_buffer, in_buffer_length, in_pos, '\"');
	in_pos = strCopyConstString(out_buffer, in_buffer_length, in_pos, ": [");

	l_add_separator = dosFalse;
	l_add_array_element_separator = dosFalse;

	return in_pos;
}

///////////////////////////////////////////////////////////////////////////////
// Ends a JSON array
dosStringLength netJSONArrayEnd(dosString out_buffer, dosStringLength in_buffer_length, dosStringLength in_pos)
{
	in_pos = strCopyConstString(out_buffer, in_buffer_length, in_pos, "] ");
	l_add_separator = dosTrue;

	return in_pos;
}

///////////////////////////////////////////////////////////////////////////////
// Add string data
dosStringLength netJSONAddString(dosString out_buffer, dosStringLength in_buffer_length, dosStringLength in_pos, dosConstString in_name, dosString in_string)
{
	in_pos = netJSONAddSeparator(out_buffer, in_buffer_length, in_pos, &l_add_separator);
	in_pos = strCopyCharacter(out_buffer, in_buffer_length, in_pos, '\"');
	in_pos = strCopyConstString(out_buffer, in_buffer_length, in_pos, in_name);
	in_pos = strCopyConstString(out_buffer, in_buffer_length, in_pos, "\": \"");
	in_pos = strCopyString(out_buffer, in_buffer_length, in_pos, in_string);
	in_pos = strCopyCharacter(out_buffer, in_buffer_length, in_pos, '\"');

	return in_pos;
}

///////////////////////////////////////////////////////////////////////////////
// Add word data
dosStringLength netJSONAddWord(dosString out_buffer, dosStringLength in_buffer_length, dosStringLength in_pos, dosConstString in_name, dosWord in_data, dosByte in_field_length, dosByte in_precision, dosByte in_options )
{
	in_pos = netJSONAddSeparator(out_buffer, in_buffer_length, in_pos, &l_add_separator);
	in_pos = strCopyCharacter(out_buffer, in_buffer_length, in_pos, '\"');
	in_pos = strCopyConstString(out_buffer, in_buffer_length, in_pos, in_name);
	in_pos = strCopyConstString(out_buffer, in_buffer_length, in_pos, "\": ");
	in_pos = strWordToStringPos(out_buffer, in_buffer_length, in_pos, in_data, in_field_length, in_precision, in_options);

	return in_pos;
}

///////////////////////////////////////////////////////////////////////////////
// Add word data
dosStringLength netJSONAddInt32(dosString out_buffer, dosStringLength in_buffer_length, dosStringLength in_pos, dosConstString in_name, dosInt32 in_data, dosByte in_field_length, dosByte in_precision, dosByte in_options )
{
	in_pos = netJSONAddSeparator(out_buffer, in_buffer_length, in_pos, &l_add_separator);
	in_pos = strCopyCharacter(out_buffer, in_buffer_length, in_pos, '\"');
	in_pos = strCopyConstString(out_buffer, in_buffer_length, in_pos, in_name);
	in_pos = strCopyConstString(out_buffer, in_buffer_length, in_pos, "\": ");
	in_pos = strInt32ToStringPos(out_buffer, in_buffer_length, in_pos, in_data, in_field_length, in_precision, in_options);

	return in_pos;
}

///////////////////////////////////////////////////////////////////////////////
// Add datetime
dosStringLength netJSONAddDateTime(dosString out_buffer, dosStringLength in_buffer_length, dosStringLength in_pos, dosConstString in_name, dosDateTime in_datetime)
{
	in_pos = netJSONAddSeparator(out_buffer, in_buffer_length, in_pos, &l_add_separator);
	in_pos = strCopyCharacter(out_buffer, in_buffer_length, in_pos, '\"');
	in_pos = strCopyConstString(out_buffer, in_buffer_length, in_pos, in_name);
	in_pos = strCopyConstString(out_buffer, in_buffer_length, in_pos, "\": ");

	in_pos = strCopyCharacter(out_buffer, in_buffer_length, in_pos, '\"');
	in_pos = krnlConvertDateToString(out_buffer, in_buffer_length, in_pos, &in_datetime, '-');
	in_pos = strCopyCharacter(out_buffer, in_buffer_length, in_pos, ' ');
	in_pos = krnlConvertTimeToString(out_buffer, in_buffer_length, in_pos, &in_datetime);
	in_pos = strCopyCharacter(out_buffer, in_buffer_length, in_pos, '\"');

	return in_pos;
}

///////////////////////////////////////////////////////////////////////////////
// Adds element separator
dosStringLength netJSONAddSeparator(dosString out_buffer, dosStringLength in_buffer_length, dosStringLength in_pos, dosBool* in_add_separator)
{
	if(*in_add_separator)
		in_pos = strCopyConstString(out_buffer, in_buffer_length, in_pos, ", ");

	*in_add_separator = dosTrue;

	return in_pos;
}

/*****************************************************************************/
/* JSON Parser routines                                                      */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
// Parse JSON file
void netJSONParse(dosString in_buffer, dosStringLength in_buffer_length, dosStringLength* inout_pos, dosBool* inout_success, netJSONCallbackInfo in_callback[], void* in_user_data)
{
	netJSONParseState state = netJSONPS_NoJSON;
	dosStringLength pos = *inout_pos;
	dosChar ch;
	dosChar key_buffer[JSON_MAX_KEY_LENGTH+1];
	dosStringLength key_buffer_pos;
	dosChar value_buffer[JSON_MAX_VALUE_LENGTH+1];
	dosStringLength value_buffer_pos;
	dosBool escape;
	dosStringLength parser_pos;

	// sanity check
	if(in_buffer == DOS_NULL || in_buffer_length == 0 || !(*inout_success))
	{
		*inout_success = dosFalse;
		return;
	}

	while(pos < in_buffer_length && state != netJSONPS_Failed && state != netJSONPS_Finished)
	{
		ch = in_buffer[pos++];

		// skip whitespaces
		if(state != netJSONPS_StoringKey && state != netJSONPS_StoringNumericValue && state != netJSONPS_StoringStringValue)
		{
			if(ch == '\n' || ch == '\r' || ch == '\t' || ch == ' ')
				continue;
		}

		// parse character depending on the state
		switch(state)
		{
			// waiting for JSON block start
			case netJSONPS_NoJSON:
				if(ch == '{')
					state = netJSONPS_LookingForKey;
				else
					state = netJSONPS_Failed;
				break;

			// looking for Key
			case netJSONPS_LookingForKey:
				switch(ch)
				{
					// key found
					case '\"':
						state = netJSONPS_StoringKey;
						key_buffer_pos = 0;
						break;

					// skip comma
					case ',':
						break;

					case '}':
						state = netJSONPS_Finished;
						break;

					default:
						state = netJSONPS_Failed;
						break;
				}
				break;

			// storing Key
			case netJSONPS_StoringKey:
				if(ch == '\"')
				{
					key_buffer[key_buffer_pos++] = '\0';
					state = netJSONPS_LookingForSeparator;
				}
				else
				{
					if(key_buffer_pos < JSON_MAX_KEY_LENGTH)
					{
						key_buffer[key_buffer_pos++] = ch;
					}
				}
				break;

			// looking for key value separator ':'
			case netJSONPS_LookingForSeparator:
				if(ch == ':')
					state = netJSONPS_LookingForValue;
				else
					state = netJSONPS_Failed;
				break;


			// looking for value
			case netJSONPS_LookingForValue:
				if(ch == '\"')
				{
					state = netJSONPS_StoringStringValue;
					value_buffer_pos = 0;
					escape = dosFalse;
				}
				else
				{
					if(charIsDigit(ch))
					{
						state = netJSONPS_StoringNumericValue;
						value_buffer_pos = 0;
					}
					else
						state = netJSONPS_Failed;
				}
				break;

			// store numeric value
			case netJSONPS_StoringNumericValue:
				if(charIsDigit(ch))
				{
					if(value_buffer_pos < JSON_MAX_VALUE_LENGTH)
					{
						value_buffer[value_buffer_pos++] = ch;
					}
				}
				else
				{
					parser_pos = 0;
					value_buffer[value_buffer_pos] = '\0';
					state = CallJSONParserCallbackFunction(key_buffer, value_buffer, JSON_MAX_VALUE_LENGTH, &parser_pos, netJSONPS_LookingForKey, in_callback, in_user_data);
				}
				break;

			// store string value
			case netJSONPS_StoringStringValue:
				switch(ch)
				{
					// escape character
					case '\\':
						escape = !escape;
						break;


					// string end character
					case '\"':
						if(!escape)
						{
							parser_pos = 0;
							value_buffer[value_buffer_pos] = '\0';
							state = CallJSONParserCallbackFunction(key_buffer, value_buffer, JSON_MAX_VALUE_LENGTH, &parser_pos, netJSONPS_LookingForKey, in_callback, in_user_data);
						}
						break;

					// store character and handle escape characters
					default:
						if(escape)
						{
							switch(charToLower(ch))
							{
								case 'n':
									ch = '\n';
									break;

								case 't':
									ch = '\t';
									break;

								case 'b':
									ch = '\b';
									break;

								case 'f':
									ch = '\f';
									break;

								default:
									state = netJSONPS_Failed;
									break;
							}

							escape = dosFalse;
						}
						break;

				}

				// store character
				if(!escape && state == netJSONPS_StoringStringValue)
				{
					if(value_buffer_pos < JSON_MAX_VALUE_LENGTH)
					{
						value_buffer[value_buffer_pos++] = ch;
					}
				}
				break;
		}
	}

	// update output variables
	*inout_success = (state == netJSONPS_Finished);
	*inout_pos = pos;
}

///////////////////////////////////////////////////////////////////////////////
// Calls JSON parser callback function
static netJSONParseState CallJSONParserCallbackFunction(dosString in_key, dosString in_value_buffer, dosStringLength in_value_buffer_length, dosStringLength* inout_pos, netJSONParseState in_state, netJSONCallbackInfo in_callback[], void* in_user_data)
{
	dosByte i;
	dosBool success = dosTrue;

	// find callback
	i = 0;
	while(in_callback[i].Key != DOS_NULL)
	{
		if( strCompareConstString(in_key, in_callback[i].Key) == 0 )
		{
			in_callback[i].Callback(in_value_buffer, in_value_buffer_length, inout_pos, &success, in_user_data);
			break;
		}
		i++;
	}

	if(success)
		return in_state;
	else
		return netJSONPS_Failed;
}
